Skip to content

[SPARK-60716][CONNECT] Convert explicit pandas date columns through Arrow - #138

Closed
dbtsai wants to merge 53 commits into
apache:masterfrom
dbtsai:codex/spark-60716-columnar-pandas-date
Closed

dbtsai wants to merge 53 commits into
apache:masterfrom
dbtsai:codex/spark-60716-columnar-pandas-date

Conversation

@dbtsai

@dbtsai dbtsai commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

Following #121 (SPARK-60676) and #128 (SPARK-60685), createDataFrame(pandas_df, schema) with an explicit StructType of DateType fields now converts object columns of exact datetime.date values and None, of at least 10,000 rows, through the Arrow importer (date32), instead of materializing Python rows. NaN, NaT, pd.NA, datetime and other objects, None in a non-nullable field, smaller frames, inferred schemas and customized inputs keep the row path, as for #121.

It is stacked on #208 (SPARK-60720), which is stacked on #207 (SPARK-60727): the four pandas paths add their hooks to the same dispatcher and their functions and tests to the same files. Until those merge, its diff includes theirs. The copies of #128's and #134's commits that this branch carried are on master now, so they no longer show in the diff.

In the merge with master and #208: the capture mock of #128's calendar-range test now serves the Arrow importer's settings, as its explicit pandas case has more than 10,000 rows and goes through the importer; and, as for #121, a test that required a second row conversion to read Index._data exactly as often as the first now checks that the path declines without more reads than the row path.

Why are the changes needed?

SPARK-60716: converting each date through Python rows dominates createDataFrame of large date frames. Measured on macOS against a local Spark 4.3.0 server, as the client CPU time of createDataFrame of a pandas DataFrame of 200,000 rows of two date columns with an explicit schema (median of 5): 213 ms through the row path with #128, 39 ms with this change (official PySpark 4.3.0: 34 ms). The results are the same, date.min, date.max and nulls included.

Does this PR introduce any user-facing change?

No, other than speed.

How was this patch tested?

  • New tests in python/tests/test_arrow_handoff.py, against the mock Arrow input service, check the date32 values of the IPC, nulls, names, nullability and metadata, the row threshold, and that each unsupported or customized input keeps the row path with its errors.
  • Against the current master, merged with [SPARK-60727][CONNECT] Build explicit double pandas input from columns #207 and [SPARK-60720][CONNECT] Encode guarded pandas string columns directly in Rust #208 into this branch, and a local Spark 4.3.0 server: the lib and every integration test of apache-spark-connect and apache-spark-connect-core, e2e included (789 passed), the build of all tests with wasm-udf, the offline Python suites (1,775 passed, 8 skipped), the live drop-in and Arrow suites (86 passed) and the live cases (10 passed). cargo fmt --all --check passes.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: OpenAI Codex (GPT-6); the merge with master and the stacked PRs, the review follow-ups and the measurement by Claude Opus 5.5

This pull request and its description were written by Isaac.

dbtsai and others added 30 commits November 5, 2026 15:59
…mantics

Use explicit LongType schema names when constructing Arrow columns. Limit the
columnar path to concrete pandas/NumPy arrays and native schema descriptors,
including authentic converter aliases retained by already imported modules.
Keep nullable values in non-nullable fields on the native row path so local
validation still precedes configuration RPCs. Support warm Arrow importers that
resolve converters locally.

Add mock-RPC regressions for all emitted integer cells, schema metadata and
nullability, custom conversion hooks, and repeated warm construction.

Co-authored-by: Isaac <no-reply@databricks.com>
Build the subclass-label fallback fixture with an explicit object Index and
assert its concrete label type. Pandas 3 otherwise normalizes constructor
labels to builtin strings before the unchanged integer helper sees them.

The original regression passes on pandas 2.3.3 and fails on 3.1.0; the fixed
regression and full CI-selected Arrow/drop-in suites pass on both versions.
Runtime Python and native sources remain unchanged.

Co-authored-by: Isaac <no-reply@databricks.com>
Decline noncanonical columns indexes and their direct string-storage hooks before the integer fast path inspects labels or normalizes cells. Keep native tolist errors and mutations in their original order and cover public construction on both pandas versions.

Co-authored-by: Isaac <no-reply@databricks.com>
Decline class-level columns storage descriptors before reading labels. Keep the single native columns storage read before pandas normalization, and cover public construction with inferred, named and explicit schemas on both pandas versions.

Co-authored-by: Isaac <no-reply@databricks.com>
Avoid loading optional Arrow label modules for fallback inputs, and decline
integer Arrow conversion when the loaded importer AtomicType alias is absent.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Read the three mutable Arrow normalization settings in one Get request per nonempty construction while retaining empty-input and transport behavior.

Co-authored-by: Isaac <no-reply@databricks.com>
…rrow

Conservatively accept ordinary object-column frames with exact built-in dates and explicit DateType schemas. Preserve native row conversion for customized schema descriptors and unsupported values.

Co-authored-by: Isaac <no-reply@databricks.com>
Use the measured 10000-row threshold to avoid config RPC overhead on smaller inputs. Cover the fast-path boundary and load fresh-process test fixtures by exact source path for CI portability.

Co-authored-by: Isaac <no-reply@databricks.com>
Use the shared protocol fixture so fresh-process descriptor regressions skip consistently with the Arrow suite when protoc is unavailable.

Co-authored-by: Isaac <no-reply@databricks.com>
Require concrete pandas NumPy storage and unchanged converter aliases before selecting the date Arrow path. Preserve custom array values and native fallback error ordering.

Co-authored-by: Isaac <no-reply@databricks.com>
Keep astype, notna and where class replacements and instance hooks on the native row path. Authenticate ordinary pandas normalization methods before columnar date conversion.

Co-authored-by: Isaac <no-reply@databricks.com>
Decline customized column label storage before date column inspection so native row conversion retains hook values, exceptions, and normalization order.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Build on the canonical pandas Index and integer guards from PR121.
Serialize eligible native float64 columns to inline Arrow IPC without row
materialization, preserving explicit schema and pandas missing values.

Include the shared internal inline IPC bridge and public Float coverage.

Co-authored-by: Isaac <no-reply@databricks.com>
dbtsai and others added 12 commits November 5, 2026 16:13
Remove the extra blank line at EOF copied from the donor suffix.
All helper function bodies remain byte-identical.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@dbtsai
dbtsai force-pushed the codex/spark-60716-columnar-pandas-date branch from 20e8ec6 to 9135b9b Compare November 6, 2026 02:09
HyukjinKwon and others added 10 commits November 6, 2026 19:58
…float64

Master has apache#121 (SPARK-60676), which this branch is stacked on, with review changes; take
master's versions of the files it touched and apply this branch's float64 changes on them.
Keep master's create_dataframe_from_columns next to the inline IPC bridge.

Co-authored-by: Isaac <no-reply@databricks.com>
…h never reaches, and document the path

The float64 path builds its Arrow table and IPC itself, so the checks of the converters of
pyspark.sql.pandas.types and of the Arrow importer guarded code it does not call, and
imported pyspark.sql.pandas.types for nothing. Drop them, with the three tests that only
exercised them (they pass with either path), and describe the path in the architecture
docs.

Co-authored-by: Isaac <no-reply@databricks.com>
…th in the float64 test

As in the int64 and timestamp tests: a later row conversion of the same frame reads
Index._data fewer times than the first, as pandas caches some reads.

Co-authored-by: Isaac <no-reply@databricks.com>
…ings

The float64 branch (the replacement of apache#143, SPARK-60727) carries the merge with master,
which has apache#121 (SPARK-60676), and the same inline IPC bridge as this branch. Take its
versions of the files this branch shares with it, and add the string path after the
float64 one: the dispatcher hook, prepare_string_input and the string tests.

Co-authored-by: Isaac <no-reply@databricks.com>
…onverter checks the string path never reaches

_pandas_string_ipc encoded every string to UTF-8 in a validation pass and again while
building; build in one pass and fall back on the first string that cannot be encoded.
prepare_string_input calls the Rust encoder with the native schema, so the checks of the
aliases of the Arrow importer and of pyspark.sql.pandas.types guarded code it does not
call; drop them, with the two tests that only exercised them.

Co-authored-by: Isaac <no-reply@databricks.com>
…th in the string test

As in the int64, timestamp and float64 tests: a later row conversion of the same frame
reads Index._data fewer times than the first, as pandas caches some reads.

Co-authored-by: Isaac <no-reply@databricks.com>
…float64

Master has apache#130 (SPARK-60686), which adds its timestamp path at the same places: keep it,
and add the float64 hook after the timestamp one, and the float64 functions and tests
after the timestamp ones.

Co-authored-by: Isaac <no-reply@databricks.com>
…ings

Co-authored-by: Isaac <no-reply@databricks.com>
Stack this on the replacements of apache#143 and apache#144 (apache#207 and apache#208), on the current master,
which has apache#121, apache#128, apache#130 and apache#134 that this branch carried copies of: take the stacked
branch's files, and add the date hook after the string one, and the date functions and
tests after theirs.

Co-authored-by: Isaac <no-reply@databricks.com>
…ct date capture mock, and do not pin the reads of master's pandas row path

The explicit DateType pandas case of test_exact_date_ipc_calendar_range has more than
10,000 rows, so it now goes through the Arrow importer, which reads its settings with a
Config RPC that the capture mock did not serve. As in the other pandas tests, a later row
conversion of the same frame reads Index._data fewer times than the first.

Co-authored-by: Isaac <no-reply@databricks.com>
@HyukjinKwon
HyukjinKwon marked this pull request as ready for review November 6, 2026 12:28
Master has apache#207 (SPARK-60727) and apache#208 (SPARK-60720), squashed, which this branch had
through merges: keep this branch's date additions after the string ones at each place
they meet.

Co-authored-by: Isaac <no-reply@databricks.com>
HyukjinKwon added a commit that referenced this pull request Nov 7, 2026
…s dates

### What changes were proposed in this pull request?

Use each explicit pandas date column's exact NumPy backing array for both eligibility validation and Arrow conversion. Add regressions for customized `Series.values` and `Series.to_numpy`, and document the existing date importer path.

### Why are the changes needed?

Follow-up to #138 / SPARK-60716. For 10,000 stored dates of `2020-01-01`, a customized `Series.values` returning `2021-02-03` made the fast path validate the stored cells but emit the replacement dates. This client's previous DataFrame row conversion retained `2020-01-01`. Validation and serialization now consume the same concrete storage.

Canonical pandas column extraction through `iloc` and `Series.array` remains trusted, consistent with the existing string path; this change does not authenticate every mutable pandas descriptor.

### Does this PR introduce _any_ user-facing change?

Yes. Customized Series accessors no longer replace the validated date cells or run during this optimization. Ordinary date values, null handling, names, thresholds and transport routing are preserved.

### How was this patch tested?

- Both new accessor regressions fail against the landed helper; the updated helper passes all 26 selected helper cases (865 deselected).
- Independent expected offsets and null masks pass for 30,000 cells spanning Python's full date calendar range; the regression checks Arrow IPC roundtrips and unchanged row normalization.
- Matching official PySpark 4.3.0 conversion passes 10,000 ordinary full-range date/null values. The custom-accessor regression targets this client's previous row-path contract.
- `git diff --check` passes. Genuine local Claude Code reviewed the full landed change and the complete follow-up patch; the final source review approved with no blockers and documented nonblocking limitations/nits.

**Draft validation limitation:** helper checks use installed native schema types, not a fresh candidate extension. Cargo/Rust/protoc are unavailable on this host, so the required Cargo lib/all-integration/wasm compile/fmt/Clippy checks, fresh extension, native mock/live tests and full official parity/API gates remain pending. No overall validation convergence is claimed.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: OpenAI Codex (GPT-6); reviewed with Claude Code 2.1.314.

This pull request and its description were written by Isaac.

Closes #278 from dbtsai/codex/postmerge-pr-138-date-backing.

Lead-authored-by: DB Tsai <dbtsai@dbtsai.com>
Co-authored-by: Hyukjin Kwon <hyukjin.kwon@databricks.com>
Signed-off-by: Hyukjin Kwon <hyukjin.kwon@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants